# -*- coding: utf-8 -*-
import random
import sys
from abc import ABC, abstractmethod
from enum import Enum

import requests
from PyQt5 import QtWidgets, QtTest
from PyQt5.QtCore import QDateTime
from PyQt5.QtWidgets import QMessageBox

from gui import Ui_MainWindow
from jd_assistant_app import JD
from jd_assistant_m import JD_m
from util import get_current_time


# 使用枚举定义接口类型
class ApiType(Enum):
    APP = 1  # app
    M = 2  # m端


# 商品模式
class GoodType(Enum):
    CART = 1  # 加入购物车模式
    SECKILL = 2  # 抢购模式
    DEPOSIT = 3  # 定金预售模式


# 下单模式
class OrderModel(Enum):
    REGULAR = 1  # 定时下单
    MONITOR = 2  # 直接监控下单
    MONITOR_BY_TIME = 3  # 定时转监控下单
    REGULAR_NORMAL = 4  # 普通定时下单
    SKU_PACK = 5  # 多商品模式


class OrderStrategy(ABC):
    """订单处理策略基类"""

    def __init__(self, assistant):
        self.assistant = assistant
        self.ui = assistant.ui
        self.assistant_app = assistant.assistant_app
        self.assistant_m = assistant.assistant_m
        self.api_type = assistant.api

    @abstractmethod
    def execute(self, sku, st, num, interval, area, ui):
        pass

    def check_login(self, sku, ui):
        """检查登录状态"""
        if self.api_type == ApiType.APP.value:
            return self.assistant_app.login(sku=sku, ui=ui)
        else:
            return self.assistant_m.login(sku=sku, ui=ui)

    def monitor_stock(self, sku, num, interval, max_attempts, success_callback):
        """通用的库存监控逻辑"""
        self.ui.console.append("{0}   库存监控中".format(get_current_time()))

        for i in range(max_attempts):
            try:
                if self.assistant_app.get_stock(sku=sku):
                    self.ui.console.append(
                        "{0}   {1}当前有货,准备试提交订单".format(get_current_time(), sku))

                    if success_callback(sku, num):
                        self.ui.console.append("{0}   程序停止".format(get_current_time()))
                        return True

                    QtTest.QTest.qWait(5000)
                    self.ui.console.append("{0}   库存监控中".format(get_current_time()))
                else:
                    QtTest.QTest.qWait(interval * 1000)
            except Exception as e:
                self.ui.console.append("{0}   请求出错: {1}".format(get_current_time(), str(e)))
                QtTest.QTest.qWait(1000)

        self.ui.console.append("{0}   已经按设置监控次数监控完毕".format(get_current_time()))
        return False


# App端策略实现
class AppCartRegularStrategy(OrderStrategy):
    """App端加车模式定时下单策略"""

    def execute(self, sku, st, num, interval, area, ui):
        if not self.check_login(sku, ui):
            ui.console.append("{0}   用户未登录".format(get_current_time()))
            return False

        if self.assistant_app.submitOrder_byTime(sku=sku, buy_time=st, num=num):
            ui.console.append("{0}   程序停止".format(get_current_time()))
            return True

        if self.assistant.times > 0:
            ui.console.append("{0}   第一次定时执行完毕,5s后开始监控库存进行提交".format(get_current_time()))
            QtTest.QTest.qWait(5000)
            self.assistant_app.currentOrder(sku=sku, num=num)
            return self.monitor_stock(
                sku=sku,
                num=num,
                interval=interval,
                max_attempts=self.assistant.times,
                success_callback=self.assistant_app.submitOrder
            )
        return False


class AppSeckillRegularStrategy(OrderStrategy):
    """App端抢购模式定时下单策略"""

    def execute(self, sku, st, num, interval, area, ui):
        if not self.check_login(sku, ui):
            ui.console.append("{0}   用户未登录".format(get_current_time()))
            return False
        if self.assistant_app.seckill_byTime(sku=sku, buy_time=st, num=num):
            ui.console.append("{0}   程序停止".format(get_current_time()))
            return True
        if self.assistant.times > 0:
            ui.console.append("{0}   第一次定时执行完毕,开始监控库存进行提交".format(get_current_time()))
            self.assistant_app.sekill_submit(sku=sku, num=num)
            return self.monitor_stock(
                sku=sku,
                num=num,
                interval=interval,
                max_attempts=self.assistant.times,
                success_callback=self.assistant_app.sekill_submit
            )
        return False


class AppDepositRegularStrategy(OrderStrategy):
    """App端定金预售定时下单策略"""

    def execute(self, sku, st, num, interval, area, ui):
        if not self.check_login(sku, ui):
            ui.console.append("{0}   用户未登录".format(get_current_time()))
            return False

        if self.assistant_app.submitOrder_deposit_byTime(sku=sku, buy_time=st):
            ui.console.append("{0}   程序停止".format(get_current_time()))
            return True

        if self.assistant.times > 0:
            ui.console.append("{0}   第一次定时执行完毕,5s后开始监控库存进行提交".format(get_current_time()))
            QtTest.QTest.qWait(5000)
            self.assistant_app.submitOrder_deposit(sku=sku, num=num)
            return self.monitor_stock(
                sku=sku,
                num=num,
                interval=interval,
                max_attempts=self.assistant.times,
                success_callback=lambda s, n: self.assistant_app.submitOrder_deposit(sku=s)
            )
        return False


class AppMutipleGoodsRegularStrategy(OrderStrategy):
    """App端多商品定时下单策略"""

    def execute(self, sku, st, num, interval, area, ui):
        if not self.check_login(sku, ui):
            ui.console.append("{0}   用户未登录".format(get_current_time()))
            return False

        if self.assistant_app.submitOrder_skuPack_byTime(sku=sku, buy_time=st, num=num):
            ui.console.append("{0}   程序停止".format(get_current_time()))
            return True

        if self.assistant.times > 0:
            ui.console.append("{0}   第一次定时执行完毕,2s后转购物车查询监控提交".format(get_current_time()))
            QtTest.QTest.qWait(2000)
            for i in range(self.assistant.times):
                try:
                    if self.assistant_app.cartCheckAll():
                        self.ui.console.append(
                            "{0}   准备试提交订单".format(get_current_time()))
                        self.assistant_app.currentOrderSkuPack()
                        if self.assistant_app.submitOrder(sku=sku, num=num):
                            self.ui.console.append("{0}   程序停止".format(get_current_time()))
                            return True
                        else:
                            QtTest.QTest.qWait(5000)
                            self.ui.console.append("{0}   购物车查询中".format(get_current_time()))
                    else:
                        QtTest.QTest.qWait(interval * 1000)
                except Exception:
                    self.ui.console.append("{0}   请求出错".format(get_current_time()))
                    QtTest.QTest.qWait(1000)
            self.ui.console.append("{0}   已经按设置监控次数监控完毕".format(get_current_time()))
        return False


# M端策略实现
class MCartRegularStrategy(OrderStrategy):
    """M端加车模式定时下单策略"""

    def execute(self, sku, st, num, interval, area, ui):
        if not self.check_login(sku, ui):
            return False

        if self.assistant_m.submit_order_bytime(buy_time=st, skuid=sku):
            ui.console.append("{0}   程序停止".format(get_current_time()))
            return True

        if self.assistant.times > 0:
            ui.console.append("{0}   第一次定时执行完毕,5s后开始监控库存进行提交".format(get_current_time()))
            QtTest.QTest.qWait(5000)
            self.assistant_m.submit_balance_getCurrentOrder_m(skuid=sku)
            return self.monitor_stock(
                sku=sku,
                num=num,
                interval=interval,
                max_attempts=self.assistant.times,
                success_callback=lambda s, n: self.assistant_m.submit_order(skuid=s)
            )
        return False


class MSeckillRegularStrategy(OrderStrategy):
    """M端抢购模式定时下单策略"""

    def execute(self, sku, st, num, interval, area, ui):
        if not self.check_login(sku, ui):
            return False

        if self.assistant_m.seckill_submit_order_bytime(buy_time=st, skuid=sku):
            ui.console.append("{0}   程序停止".format(get_current_time()))
            return True

        if self.assistant.times > 0:
            ui.console.append("{0}   第一次定时执行完毕,5s后开始监控库存进行提交".format(get_current_time()))
            QtTest.QTest.qWait(5000)
            return self.monitor_stock(
                sku=sku,
                num=num,
                interval=interval,
                max_attempts=self.assistant.times,
                success_callback=lambda s, n: self.assistant_m.seckill_submit_order(skuid=s)
            )
        return False


class MDepositRegularStrategy(OrderStrategy):
    """m端定金预售定时下单策略"""

    def execute(self, sku, st, num, interval, area, ui):
        if not self.check_login(sku, ui):
            ui.console.append("{0}   用户未登录".format(get_current_time()))
            return False

        if self.assistant_m.yushou_dingjin_buytime(sku=sku, buy_time=st, area=self.assistant_m.parse_area(areaid=area)):
            ui.console.append("{0}   程序停止".format(get_current_time()))
            return True

        if self.assistant.times > 0:
            ui.console.append("{0}   第一次定时执行完毕,5s后开始监控库存进行提交".format(get_current_time()))
            QtTest.QTest.qWait(5000)
            return self.monitor_stock(
                sku=sku,
                num=num,
                interval=interval,
                max_attempts=self.assistant.times,
                success_callback=lambda s, n: self.assistant_m.booking_good(sku=s)
            )
        return False


# 创建策略工厂
class OrderStrategyFactory:
    @staticmethod
    def create_strategy(assistant) -> OrderStrategy:
        strategies = {
            # 定义所有可能的策略组合
            # App端策略
            (ApiType.APP.value, GoodType.CART.value, OrderModel.REGULAR.value): AppCartRegularStrategy(assistant),
            (ApiType.APP.value, GoodType.SECKILL.value, OrderModel.REGULAR.value): AppSeckillRegularStrategy(assistant),
            (ApiType.APP.value, GoodType.DEPOSIT.value, OrderModel.REGULAR.value): AppDepositRegularStrategy(assistant),
            (ApiType.APP.value, GoodType.CART.value, OrderModel.SKU_PACK.value): AppMutipleGoodsRegularStrategy(
                assistant),
            # M端策略
            (ApiType.M.value, GoodType.CART.value, OrderModel.REGULAR.value): MCartRegularStrategy(assistant),
            (ApiType.M.value, GoodType.SECKILL.value, OrderModel.REGULAR.value): MSeckillRegularStrategy(assistant),
            (ApiType.M.value, GoodType.DEPOSIT.value, OrderModel.REGULAR.value): MDepositRegularStrategy(assistant),
        }
        api_type = ApiType.APP.value if assistant.api == 1 else ApiType.M.value
        good_type = assistant.good_type
        order_model = assistant.order_model
        return strategies.get((api_type, good_type, order_model))
    # def get_strategy(api, good_type, order_model, strategies=None):
    #     return strategies.get((api, good_type, order_model))


class assistant(QtWidgets.QMainWindow):
    def __init__(self):
        QtWidgets.QMainWindow.__init__(self)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)
        self.ui.dateTimeEdit.setDateTime(QDateTime.currentDateTime())
        self.ui.start.clicked.connect(self.set_params)
        self.ui.stop.clicked.connect(self.reset)
        self.ui.discern.clicked.connect(self.commodity_good_mode)
        self.assistant_app = JD()
        self.assistant_m = JD_m()
        self.ui.console.append("{0}   当前app版本:{1}".format(get_current_time(), self.assistant_app.clientVersion))
        self.api = ''
        self.good_type = ''
        self.num = ''
        self.sku = ''
        self.buy_time = ''
        # 下单模式-定时，定时转监控，直接转监控下单，多商品模式
        self.order_model = ''
        self.get_current_ip_address()
        self.get_delay()
        self.frequency = 0
        self.area = self.assistant_app.area
        self.parse_areaid = ''
        self.pause = False
        self.times = 0

    def get_current_ip_address(self):
        response = requests.get("https://www.ipplus360.com/getIP")
        data = response.json()
        ip = data['data']
        self.assistant_app.currentIp = ip
        self.assistant_m.currentIp = ip
        self.ui.console.append("{0}   当前IP地址:{1}".format(get_current_time(), ip))

    def reset(self):
        self.ui.start.setEnabled(True)

    def set_params(self):
        self.set_api_type()
        self.set_good_type()
        self.set_order_model()

        self.sku = self.ui.sku.toPlainText().replace(" ", "")
        self.num = self.ui.num.toPlainText()
        self.buy_time = self.ui.dateTimeEdit.text() + '.' + self.ui.ms.toPlainText()
        self.parse_areaid = self.assistant_m.parse_area(areaid=self.area)
        self.times = int(self.ui.times.toPlainText())
        self.frequency = int(self.ui.interval.toPlainText().replace(" ", ""))

        self.run_program(sku=self.sku, st=self.buy_time, num=self.num,
                         interval=self.frequency, area=self.area, ui=self.ui)

    def set_api_type(self):
        if self.ui.app.isChecked():
            self.api = 1
        elif self.ui.m.isChecked():
            self.api = 2

    def set_good_type(self):
        if self.ui.mode_cart.isChecked():
            self.good_type = 1
        elif self.ui.mode_seckill.isChecked():
            self.good_type = 2
        elif self.ui.mode_deposit.isChecked():
            self.good_type = 3

    def set_order_model(self):
        if self.ui.regular_order.isChecked():
            self.order_model = 1
        elif self.ui.monitor_order.isChecked():
            self.order_model = 2
        elif self.ui.monitor_oder_bytime.isChecked():
            self.order_model = 3
        elif self.ui.regular_order_normal.isChecked():
            self.order_model = 4
        elif self.ui.sku_pack.isChecked():
            self.order_model = 5

    def commodity_good_mode(self):
        sku = self.ui.sku.toPlainText().replace(" ", "")
        if sku == '':
            return
        else:
            self.ui.Commodity_model.setText("功能已下线")
            # if self.assistant_app.commodity_mode(sku=sku):
            #     self.ui.Commodity_model.setText("加车")
            # else:
            #     self.ui.Commodity_model.setText("抢购")

    # 获取本地与京东服务器时间误差
    def get_delay(self):
        try:
            ts = self.assistant_app.get_jd_time()
            self.ui.console.append("{0}   本地与京东服务器时间相差:{1}ms".format(get_current_time(), ts[1]))
        except:
            self.ui.console.append("{0}   京东服务器时间接口异常，本次获取失败".format(get_current_time()))

    def run_program(self, sku, st, num, interval, area, ui):
        # 参数校验
        if not self.validate_input(sku):
            return
        # 处理sku格式
        sku = self.process_sku(sku)
        # 使用策略模式处理订单
        # 创建并执行策略
        strategy = OrderStrategyFactory.create_strategy(self)
        if strategy:
            strategy_instance = strategy
            strategy_instance.execute(sku, st, num, interval, area, ui)
        else:
            ui.console.append("{0}   不支持的订单模式组合".format(get_current_time()))

    def validate_input(self, sku):
        if not sku:
            self.ui.console.append(f"{get_current_time()}   请输入商品编号进行抢购")
            return False
        return True

    def process_sku(self, sku):
        if self.order_model != 5:
            sku = sku.replace('，', ',').split(',')
            return random.choice(sku)
        return sku.replace('，', ',').split(',')

    def closeEvent(self, event):
        # 询问用户是否要退出程序
        reply = QMessageBox.question(self, '退出', '确认退出吗？', QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        if reply == QMessageBox.Yes:
            # 完全退出程序
            QtWidgets.QApplication.quit()
        else:
            # 忽略关闭事件，程序继续运行
            event.ignore()


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)
    w = assistant()
    w.show()
    sys.exit(app.exec_())
